package eval

import (
	"fmt"

	"gitee.com/yanxingshuyuan/ming/ast"
	"gitee.com/yanxingshuyuan/ming/object"
)

var (
	NIL   = &object.Nil{}
	TRUE  = &object.Bool{Value: true}
	FALSE = &object.Bool{Value: false}
)

func Eval(node ast.Node, env *object.Env) object.Object {
	switch node := node.(type) {
	case *ast.Program:
		return evalProgram(node, env)
	case *ast.VarStmt:
		return evalVarStmt(node, env)
	case *ast.ExprStmt:
		return Eval(node.Expr, env)
	case *ast.BlockStmt:
		return evalStmts(node.Stmts, env)
	case *ast.FnDeclStmt:
		params := node.Params
		body := node.Body
		fn := &object.Fn{Name: node.Name.Value, Params: params, Body: body, Env: env}
		env.Set(fn.Name, fn)
		return fn
	case *ast.CallExpr:
		fn := Eval(node.Fn, env)
		if isError(fn) {
			return fn
		}
		args := evalExprs(node.Args, env)
		if len(args) == 1 && isError(args[0]) {
			return args[0]
		}

		return applyFunction(fn, args)
	case *ast.IfExpr:
		return evalIfExpr(node, env)
	case *ast.ForExpr:
		return evalForExpr(node, env)
	case *ast.IntLiteral:
		return &object.Int{Value: int(node.Value)}
	case *ast.BoolLiteral:
		return makeBoolObj(node.Value)
	case *ast.Ident:
		return evalIdent(node, env)
	case *ast.PrefixExpr:
		right := Eval(node.Right, env)
		return evalPrefixExpr(node.Operator, right)
	case *ast.InfixExpr:
		// 单独处理赋值表达式
		if node.Operator == "=" {
			if left, ok := node.Left.(*ast.Ident); ok {
				right := Eval(node.Right, env)
				if isError(right) {
					return right
				}
				env.Set(left.Value, right)
				return right
			}
			return newError("left of assignment is not an identifier!")
		}
		// 其他运算表达式
		left := Eval(node.Left, env)
		if isError(left) {
			return left
		}
		right := Eval(node.Right, env)
		if isError(right) {
			return right
		}
		return evalInfixExpr(node.Operator, left, right)
	}
	return nil
}

func evalProgram(prog *ast.Program, env *object.Env) object.Object {
	var result object.Object

	for _, stmt := range prog.Stmts {
		result = Eval(stmt, env)
	}

	return result
}

func isError(obj object.Object) bool {
	if obj != nil {
		return obj.Type() == object.ERROR_OBJ
	}
	return false
}

func evalInfixExpr(op string, left, right object.Object) object.Object {
	switch {
	case left.Type() == object.INT_OBJ && right.Type() == object.INT_OBJ:
		return evalIntInfixExpr(op, left, right)
	case op == "==":
		return makeBoolObj(left == right)
	case op == "!=":
		return makeBoolObj(left != right)
	case left.Type() != right.Type():
		return newError("type mismatch: %s %s %s", left.Type(), op, right.Type())
	default:
		return newError("unknown operator:%s %s %s", left.Type(), op, right.Type())
	}
}

func newError(format string, a ...interface{}) *object.Error {
	return &object.Error{Message: fmt.Sprintf(format, a...)}
}

func evalIntInfixExpr(op string, left, right object.Object) object.Object {
	leftVal := left.(*object.Int).Value
	rightVal := right.(*object.Int).Value

	switch op {
	case "+":
		return &object.Int{Value: leftVal + rightVal}
	case "-":
		return &object.Int{Value: leftVal - rightVal}
	case "*":
		return &object.Int{Value: leftVal * rightVal}
	case "/":
		return &object.Int{Value: leftVal / rightVal}
	case "<":
		return makeBoolObj(leftVal < rightVal)
	case ">":
		return makeBoolObj(leftVal > rightVal)
	case "==":
		return makeBoolObj(leftVal == rightVal)
	case "!=":
		return makeBoolObj(leftVal != rightVal)
	default:
		return newError("unknown operator: %s %s %s", left.Type(), op, right.Type())
	}
}

func makeBoolObj(val bool) *object.Bool {
	if val {
		return TRUE
	} else {
		return FALSE
	}
}

func evalPrefixExpr(op string, right object.Object) object.Object {
	switch op {
	case "!":
		return evalNotPrefixExpr(right)
	case "-":
		return evalSubPrefixExpr(right)
	default:
		return newError("unknown operator: %s %s", op, right.Type())
	}
}

func evalNotPrefixExpr(right object.Object) object.Object {
	switch right {
	case TRUE:
		return FALSE
	case FALSE:
		return TRUE
	case NIL:
		return TRUE
	default:
		return FALSE
	}
}

func evalSubPrefixExpr(right object.Object) object.Object {
	if right.Type() == object.INT_OBJ {
		return newError("unknown operator: -%s", right.Type())
	}

	val := right.(*object.Int).Value
	return &object.Int{Value: -val}
}

func evalVarStmt(stmt *ast.VarStmt, env *object.Env) object.Object {
	var result object.Object

	value := Eval(stmt.Value, env)
	if isError(value) {
		return value
	}

	env.Set(stmt.Name.Value, value)

	return result
}

func evalIdent(node *ast.Ident, env *object.Env) object.Object {
	val, ok := env.Get(node.Value)
	if !ok {
		return newError("identifier not found: " + node.Value)
	}
	return val
}

func evalStmts(stmts []ast.Stmt, env *object.Env) object.Object {
	var result object.Object
	for _, stmt := range stmts {
		result = Eval(stmt, env)
	}
	return result
}

func evalIfExpr(ie *ast.IfExpr, env *object.Env) object.Object {
	condition := Eval(ie.Condition, env)

	if isTruthy(condition) {
		return Eval(ie.Then, env)
	} else if ie.Else != nil {
		return Eval(ie.Else, env)
	} else {
		return NIL
	}
}

func isTruthy(obj object.Object) bool {
	switch obj {
	case TRUE:
		return true
	case FALSE:
		return false
	case NIL:
		return false
	default:
		return true
	}
}

func evalForExpr(fe *ast.ForExpr, env *object.Env) object.Object {
	var result object.Object = NIL

	if fe.Initial != nil {
		Eval(fe.Initial, env)
	}

	for Eval(fe.Condition, env) == TRUE {
		result = Eval(fe.Body, env)
		if fe.Step != nil {
			Eval(fe.Step, env)
		}
	}

	return result
}

func evalExprs(exprs []ast.Expr, env *object.Env) []object.Object {
	var results []object.Object

	for _, e := range exprs {
		evaled := Eval(e, env)
		if isError(evaled) {
			return []object.Object{evaled}
		}
		results = append(results, evaled)
	}

	return results
}

func applyFunction(fn object.Object, args []object.Object) object.Object {
	fun, ok := fn.(*object.Fn)
	if !ok {
		return newError("not a function: %s", fn.Type())
	}

	childEnv := object.NewChildEnv(fun.Env)

	for i, param := range fun.Params {
		childEnv.Set(param.Name.Value, args[i])
	}

	evaled := Eval(fun.Body, childEnv)
	return evaled
}
